using System;
using System.Timers;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Campus
{
    public abstract class Unit : BaseEntity
    {
        #region Type Definitions

        public enum UnitState
        {
            Idle,
            WalkLeft, WalkRight, WalkUp, WalkDown,
            AttackLeft, AttackRight, AttackUp, AttackDown,
            Die
        };

        public enum UnitPathTraversalState
        {
            Stopped = 0x00, 
            Moving  = 0x01, 
            Paused  = 0x02
        };

        #endregion

        #region Fields and Properties

        private static int m_prioritySeed = 0;

        /// <summary>
        /// The current traversal state of this unit.  Used for collision handling with other base entities.
        /// </summary>
        public UnitPathTraversalState TraversalState { get; private set; }

        /// <summary>
        /// The current destination of this unit.
        /// </summary>
        protected Vector2 Destination { get; set; }

        /// <summary>
        /// The attack radius for this unit.
        /// </summary>
        public float AttackRadius { get; private set; }

        /// <summary>
        /// The bounding box used for attacking for this unit.
        /// </summary>
        public BoundingBox AttackBoundingBox { get; private set; }

        /// <summary>
        /// The time it takes before the unit can fire again, in milliseconds.
        /// </summary>
        public float AttackRechargeTime { get; protected set; }

        /// <summary>
        /// The current state the unit is in.
        /// </summary>
        public UnitState CurrentState { get; private set; }

        /// <summary>
        /// The speed, in pixels per second, of this unit.
        /// </summary>
        public float MoveSpeed { get; protected set; }

        /// <summary>
        /// A property which is a queue of destinations the unit should visit.
        /// </summary>
        public Queue<Vector2> DestinationQueue { get; set; }

        /// <summary>
        /// The priority of the unit used in collision handling.  This is used to prevent
        /// two units from modifying both their behaviors on a collision (only one should).
        /// </summary>
        public int Priority { get; private set; }

        /// <summary>
        /// The BoundingBox used for collisions between units as it is closer to a box than a circle.
        /// </summary>
        public BoundingBox CollisionBox { get; protected set; }

        /// <summary>
        /// Used for collisions, used to define whether a Unit is already taking action due to a collision.
        /// </summary>
        public bool IsHandlingCollisions { get; set; }

        /// <summary>
        /// Needed to make projectile creation more accurate.
        /// </summary>
        public Vector2 LeftProjectileOffset { get; protected set; }

        /// <summary>
        /// Needed to make projectile creation more accurate.
        /// </summary>
        public Vector2 RightProjectileOffset { get; protected set; }

        protected Dictionary<UnitState, Animation> m_animationDictionary;
        protected Dictionary<UnitState, TextureMapAnimation> m_mapAnimationDictionary;

        protected Rectangle m_collisionBoxRect;

        private Vector2 m_lastPosition;

        #endregion

        #region Constructors

        public Unit(Texture2D texture, Texture2D teamColorTexture, Vector2 origin, Vector2 position, 
                    int maxHitPoints, Player owner, Level level, Dictionary<String, Ability> abilities, 
                    Dictionary<String, AbilitySubMenu> subMenus)
                    : base(texture, teamColorTexture, origin, position, maxHitPoints, owner, level, abilities, subMenus)
        {
            this.m_lastPosition = new Vector2(position.X, position.Y);

            owner.Units.Add(this);

            this.AttackRechargeTime = 3000;
            this.MoveSpeed = 80;
            this.DestinationQueue = new Queue<Vector2>();
            this.TraversalState = UnitPathTraversalState.Stopped;
            this.Priority = m_prioritySeed++;
            this.IsHandlingCollisions = false;
            this.LeftProjectileOffset = Vector2.Zero;
            this.RightProjectileOffset = Vector2.Zero;

            if (Level.SelectionRectangles.ContainsKey(this.GetType()))
            {
                m_collisionBoxRect = Level.SelectionRectangles[this.GetType()];
                m_collisionBoxRect.X += 3;
                m_collisionBoxRect.Y += 3;
                m_collisionBoxRect.Width -= 6;
                m_collisionBoxRect.Height -= 6;
            }
            else
            {
                Console.WriteLine("COULD NOT FIND BOUNDINGBOX OFFSET FOR THIS UNIT!");
                m_collisionBoxRect = new Rectangle();
            }
        }

        public Unit(Animation animation, Vector2 position, int maxHitPoints, Player owner, Level level, 
                    Dictionary<String, Ability> abilities, Dictionary<String, AbilitySubMenu> subMenus) 
                    : base(animation, position, maxHitPoints, owner,level, abilities, subMenus) 
        {
            this.m_lastPosition = new Vector2(position.X, position.Y);

            owner.Units.Add(this);

            this.AttackRechargeTime = 3000;
            this.MoveSpeed = 80;
            this.DestinationQueue = new Queue<Vector2>();
            this.TraversalState = UnitPathTraversalState.Stopped;
            this.Priority = m_prioritySeed++;
            this.IsHandlingCollisions = false;

            if (Level.SelectionRectangles.ContainsKey(this.GetType()))
            {
                m_collisionBoxRect = Level.SelectionRectangles[this.GetType()];
                m_collisionBoxRect.X += 3;
                m_collisionBoxRect.Y += 3;
                m_collisionBoxRect.Width -= 6;
                m_collisionBoxRect.Height -= 6;
            }
            else
            {
                Console.WriteLine("COULD NOT FIND BOUNDINGBOX OFFSET FOR THIS UNIT!");
                m_collisionBoxRect = new Rectangle();
            }
        }

        public Unit(TextureMapAnimation animation, Vector2 position, int maxHitPoints, Player owner, Level level, 
                    Dictionary<String, Ability> abilities, Dictionary<String, AbilitySubMenu> subMenus)
                    : base(animation, position, maxHitPoints, owner, level, abilities, subMenus) 
        {
            this.m_lastPosition = new Vector2(position.X, position.Y);

            owner.Units.Add(this);

            this.AttackRechargeTime = 3000;
            this.MoveSpeed = 80;
            this.DestinationQueue = new Queue<Vector2>();
            this.TraversalState = UnitPathTraversalState.Stopped;
            this.Priority = m_prioritySeed++;
            this.IsHandlingCollisions = false;

            if (Level.SelectionRectangles.ContainsKey(this.GetType()))
            {
                m_collisionBoxRect = Level.SelectionRectangles[this.GetType()];
                m_collisionBoxRect.X += 3;
                m_collisionBoxRect.Y += 3;
                m_collisionBoxRect.Width -= 6;
                m_collisionBoxRect.Height -= 6;
            }
            else
            {
                Console.WriteLine("COULD NOT FIND BOUNDINGBOX OFFSET FOR THIS UNIT!");
                m_collisionBoxRect = new Rectangle();
            }
        }

        public Unit(Dictionary<UnitState, Animation> animationDictionary, Vector2 position, int maxHitPoints, 
                    Player owner, Level level, Dictionary<String, Ability> abilities, 
                    Dictionary<String, AbilitySubMenu> subMenus)
                    : base(animationDictionary[UnitState.Idle], position, maxHitPoints, owner, level, abilities, subMenus) 
        {
            this.m_lastPosition = new Vector2(position.X, position.Y);
            this.m_animationDictionary = animationDictionary;
            //this.Origin = new Vector2(position.X, position.Y + this.Size.Y);

            owner.Units.Add(this);

            this.AttackRechargeTime = 3000;
            this.MoveSpeed = 80;
            this.DestinationQueue = new Queue<Vector2>();
            this.TraversalState = UnitPathTraversalState.Stopped;
            this.Priority = m_prioritySeed++;

            if (Level.SelectionRectangles.ContainsKey(this.GetType()))
            {
                m_collisionBoxRect = Level.SelectionRectangles[this.GetType()];
            }
            else
            {
                Console.WriteLine("COULD NOT FIND BOUNDINGBOX OFFSET FOR THIS UNIT!");
                m_collisionBoxRect = new Rectangle();
            }
        }

        #endregion

        #region Methods

        protected override void Destroy()
        {
            // Destroy might be called multiple times if the projectile
            // hits the unit after he dies.  Therefore, return if the
            // state is already 'Die'
            if (CurrentState == UnitState.Die)
            {
                return;
            }

            this.Stop();
            m_isSelected = false;
            m_isUsable = false;
            PlayerOwner.AddUsedFood(-1);

            CurrentState = UnitState.Die;
            if (m_animationDictionary.ContainsKey(UnitState.Die))
            {
                CurrentAnimation = m_animationDictionary[CurrentState];
                CurrentAnimation.ResetAnimation();
                CurrentAnimation.Start();
            }
            else
            {
                PlayerOwner.Destroyed.Add(this);
                return;
            }

            Timer removeUnitTimer = new Timer(5000);
            removeUnitTimer.AutoReset = false;

            removeUnitTimer.Elapsed += new ElapsedEventHandler(delegate(object o, ElapsedEventArgs e)
            {
                PlayerOwner.Destroyed.Add(this);
            });

            removeUnitTimer.Start();
        }

        public override void Update(GameTime gameTime, bool checkInput, bool updateBoundingBox)
        {  
            // Only update this unit if it's actually usable
            if (m_isUsable)
            {
                UnitState oldState = CurrentState;

                //go towards destination if not attacking
                if (Position != Destination || DestinationQueue.Count > 0)
                {
                    Move(gameTime);
                }

                float differenceX = Position.X - m_lastPosition.X;
                float differenceY = Position.Y - m_lastPosition.Y;

                if (Math.Abs(differenceX) < Math.Abs(differenceY))
                {
                    if (differenceY > 0)
                    {
                        CurrentState = UnitState.WalkDown;
                    }
                    else if (differenceY < 0)
                    {
                        CurrentState = UnitState.WalkUp;
                    }
                }
                else if (differenceX > 0)
                {
                    CurrentState = UnitState.WalkRight;
                }
                else if (differenceX < 0)
                {
                    CurrentState = UnitState.WalkLeft;
                }
                else if (IsAttacking)
                {
                    AttackAbility attack = (AttackAbility)AbilityQueue.Peek();
                    BaseEntity target = (attack.TargetType == Ability.TargetTypes.BaseEntityTarget ? (BaseEntity)attack.Target : null);

                    if (target.Position.X > Position.X)
                    {
                        CurrentState = UnitState.AttackRight;
                    }
                    else if (target.Position.X <= Position.X)
                    {
                        CurrentState = UnitState.AttackLeft;
                    }
                    else if (target.Position.Y > Position.Y)
                    {
                        CurrentState = UnitState.AttackDown;
                    }
                    else
                    {
                        CurrentState = UnitState.AttackUp;
                    }

                }
                else
                {
                    CurrentState = UnitState.Idle;
                }

                if (CurrentState != oldState)
                {
                    if (m_animationDictionary != null)
                    {
                        CurrentAnimation.Pause();
                        CurrentAnimation.ResetAnimation();

                        if (m_animationDictionary.ContainsKey(CurrentState))
                        {
                            CurrentAnimation = m_animationDictionary[CurrentState];
                        }
                        else
                        {
                            CurrentAnimation = m_animationDictionary[UnitState.Idle];
                        }
                    }
                    else
                    {
                        CurrentMapAnimation.Pause();

                        if (m_mapAnimationDictionary.ContainsKey(CurrentState))
                        {
                            CurrentMapAnimation = m_mapAnimationDictionary[CurrentState];
                        }
                        else
                        {
                            CurrentMapAnimation = m_mapAnimationDictionary[UnitState.Idle];
                        }
                    }
                    CurrentAnimation.Start();
                }

                m_lastPosition.X = Position.X;
                m_lastPosition.Y = Position.Y;
            }

            m_lastPosition.X = Position.X;
            m_lastPosition.Y = Position.Y;

            UpdateCollisionBox();

            base.Update(gameTime, checkInput, true);
        }

        private void UpdateCollisionBox()
        {
            Vector2 start = new Vector2(this.Position.X + this.m_collisionBoxRect.X,
                                        this.Position.Y + this.m_collisionBoxRect.Y);

            Vector2 end = new Vector2(start.X + this.m_collisionBoxRect.Width,
                                      start.Y + this.m_collisionBoxRect.Height);

            CollisionBox = new BoundingBox(new Vector3(start, 0.0f), new Vector3(end, 0.0f));
        }

        public Rectangle GetCollisionRectangle()
        {
            Point pos = new Point((int)CollisionBox.Min.X, (int)CollisionBox.Min.Y);

            Point size = new Point((int)(CollisionBox.Max.X - CollisionBox.Min.X),
                                   (int)(CollisionBox.Max.Y - CollisionBox.Min.Y));

            return new Rectangle(pos.X, pos.Y, size.X, size.Y);
        }

        public bool CollidesWithOtherUnit(Unit otherUnit)
        {
            return this.CollisionBox.Contains(otherUnit.CollisionBox) != ContainmentType.Disjoint;
        }

        public void Move(GameTime gameTime)
        {
            Vector2 direction = (-this.Position + this.Destination);

            if (direction.Length() < 4)
            {
                // Get the next position 
                if (DestinationQueue.Count > 0)
                {
                    this.Destination = DestinationQueue.Dequeue();
                    direction = (-this.Position + this.Destination);

                    // If the new position is right on top of where we are
                    if (direction == Vector2.Zero)
                    {
                        return;
                    }
                }
                else
                {
                    // You've reached the destination (pretty much).  Set the current position
                    // to the final destination and return
                    this.Position = this.Destination;
                    this.TraversalState = UnitPathTraversalState.Stopped;
                    return;
                }
            }

            direction.Normalize();
            this.Position += (direction * MoveSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds);
            this.TraversalState = UnitPathTraversalState.Moving;
        }
        public void StopMoving() {
            if (0 < AbilityQueue.Count) {
                if (AbilityQueue.Peek() is MoveAbility) {
                    AbilityQueue.Dequeue(); ;
                }
            }
            DestinationQueue.Clear();
            Destination = Position;
        }
        public override void Stop()
        {
            this.Destination = this.Position;
            this.TraversalState = UnitPathTraversalState.Stopped;

            DestinationQueue.Clear();
            IsAttacking = false;
            base.AbilityQueue.Clear();
        }

        public MoveAbility GetMoveAbility() {
            return (MoveAbility)Abilities["Move"];
        }

        public override Vector2 GetCenter() {
            Vector2 center = new Vector2();
            center.X = Position.X + Size.X / 2;
            center.Y = Position.Y + Size.Y / 2;
            return center;
        }

        public override string ToString()
        {
            string returnString = m_name + "\n" +
                                  "HP: " + m_currentHP + "/" + m_maxHP + "\n";
            if (GetAttackAbility() != null) {
                returnString += "Attack: " + GetAttackAbility().MinimumDamage + " - " + GetAttackAbility().MaximumDamage;
            }

            return returnString;
        }

        #endregion
    }
}
